// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <ktest/ktest.h>
#include <kthread/internal/kthread.h>
#include <kthread/internal/singleton_on_kthread_once.h>
#include <kthread/internal/task_control.h>

namespace kthread {
    extern TaskControl *g_task_control;
}

namespace {

    kthread_once_t g_kthread_once_control;
    bool g_kthread_once_started = false;
    std::atomic<int> g_kthread_once_count(0);

    void init_routine() {
        kthread_usleep(2000 * 1000);
        g_kthread_once_count.fetch_add(1, std::memory_order_relaxed);
    }

    void kthread_once_task() {
        kthread_once(&g_kthread_once_control, init_routine);
        //  `init_routine' only be called once.
        ASSERT_EQ(1, g_kthread_once_count.load(std::memory_order_relaxed));
    }

    void *first_kthread_once_task(void *) {
        g_kthread_once_started = true;
        kthread_once_task();
        return nullptr;
    }


    void *other_kthread_once_task(void *) {
        kthread_once_task();
        return nullptr;
    }

    TEST(KthreadOnceTest, once) {
        kthread_t bid;
        ASSERT_EQ(0, kthread_start_background(
                &bid, nullptr, first_kthread_once_task, nullptr));
        while (!g_kthread_once_started) {
            kthread_usleep(1000);
        }
        ASSERT_NE(nullptr, kthread::g_task_control);
        int concurrency = kthread::g_task_control->concurrency();
        KLOG(INFO) << "concurrency: " << concurrency;
        ASSERT_GT(concurrency, 0);
        std::vector<kthread_t> bids(concurrency * 100);
        for (auto &id: bids) {
            ASSERT_EQ(0, kthread_start_background(
                    &id, nullptr, other_kthread_once_task, nullptr));
        }
        kthread_once_task();

        for (auto &id: bids) {
            kthread_join(id, nullptr);
        }
        kthread_join(bid, nullptr);
    }

    bool g_kthread_started = false;
    std::atomic<int> g_kthread_singleton_count(0);

    class KthreadSingleton {
    public:
        KthreadSingleton() {
            kthread_usleep(2000 * 1000);
            g_kthread_singleton_count.fetch_add(1, std::memory_order_relaxed);
        }
    };

    void get_kthread_singleton() {
        auto instance = kthread::get_leaky_singleton<KthreadSingleton>();
        ASSERT_NE(nullptr, instance);
        // Only one KthreadSingleton instance has been created.
        ASSERT_EQ(1, g_kthread_singleton_count.load(std::memory_order_relaxed));
    }

    void *first_get_kthread_singleton(void *) {
        g_kthread_started = true;
        get_kthread_singleton();
        return nullptr;
    }


    void *get_kthread_singleton(void *) {
        get_kthread_singleton();
        return nullptr;
    }

// Singleton will definitely not cause deadlock,
// even if constructor of T will hang the kthread.
    TEST(KthreadOnceTest, singleton) {
        kthread_t bid;
        ASSERT_EQ(0, kthread_start_background(
                &bid, nullptr, first_get_kthread_singleton, nullptr));
        while (!g_kthread_started) {
            kthread_usleep(1000);
        }
        ASSERT_NE(nullptr, kthread::g_task_control);
        int concurrency = kthread::g_task_control->concurrency();
        KLOG(INFO) << "concurrency: " << concurrency;
        ASSERT_GT(concurrency, 0);
        std::vector<kthread_t> bids(concurrency * 100);
        for (auto &id: bids) {
            ASSERT_EQ(0, kthread_start_background(
                    &id, nullptr, get_kthread_singleton, nullptr));
        }
        get_kthread_singleton();

        for (auto &id: bids) {
            kthread_join(id, nullptr);
        }
        kthread_join(bid, nullptr);
    }

}